package distributed

import (
	"context"
	"fmt"
	"sync"
	"time"

	protov1 "xagent/proto/v1"

	"github.com/asynkron/protoactor-go/actor"
)

// DecisionManager handles distributed decision making
type DecisionManager struct {
	system          *actor.ActorSystem
	clusterManager  *ClusterManager
	decisions       map[string]*protov1.DistributedDecision
	decisionsMutex  sync.RWMutex
	decisionTimeout time.Duration
}

// NewDecisionManager creates a new decision manager
func NewDecisionManager(system *actor.ActorSystem, clusterManager *ClusterManager) *DecisionManager {
	return &DecisionManager{
		system:          system,
		clusterManager:  clusterManager,
		decisions:       make(map[string]*protov1.DistributedDecision),
		decisionTimeout: 30 * time.Second,
	}
}

// InitiateDecision starts a new distributed decision making process
func (dm *DecisionManager) InitiateDecision(ctx context.Context, topic string, participants []string) (*protov1.DistributedDecision, error) {
	// Create decision request
	decision := &protov1.DistributedDecision{
		Id:           fmt.Sprintf("decision-%d", time.Now().UnixNano()),
		Topic:        topic,
		Participants: participants,
		Votes:        make(map[string]float32),
		Status:       protov1.DecisionStatus_DECISION_STATUS_PENDING,
		Timestamp:    time.Now().UnixNano(),
	}

	// Store decision
	dm.decisionsMutex.Lock()
	dm.decisions[decision.Id] = decision
	dm.decisionsMutex.Unlock()

	// Broadcast decision request
	if err := dm.clusterManager.BroadcastMessage(decision); err != nil {
		return nil, fmt.Errorf("failed to broadcast decision: %v", err)
	}

	// Start decision timeout
	go dm.handleDecisionTimeout(ctx, decision.Id)

	return decision, nil
}

// SubmitVote submits a vote for a decision
func (dm *DecisionManager) SubmitVote(decisionID string, nodeID string, vote float32) error {
	dm.decisionsMutex.Lock()
	defer dm.decisionsMutex.Unlock()

	decision, exists := dm.decisions[decisionID]
	if !exists {
		return fmt.Errorf("decision %s not found", decisionID)
	}

	if decision.Status != protov1.DecisionStatus_DECISION_STATUS_PENDING {
		return fmt.Errorf("decision %s is not pending", decisionID)
	}

	// Update vote
	decision.Votes[nodeID] = vote

	// Check if all participants have voted
	if len(decision.Votes) == len(decision.Participants) {
		dm.finalizeDecision(decision)
	}

	return nil
}

// GetDecision returns a decision by ID
func (dm *DecisionManager) GetDecision(decisionID string) *protov1.DistributedDecision {
	dm.decisionsMutex.RLock()
	defer dm.decisionsMutex.RUnlock()
	return dm.decisions[decisionID]
}

// Internal methods

func (dm *DecisionManager) handleDecisionTimeout(ctx context.Context, decisionID string) {
	timer := time.NewTimer(dm.decisionTimeout)
	defer timer.Stop()

	select {
	case <-ctx.Done():
		return
	case <-timer.C:
		dm.decisionsMutex.Lock()
		defer dm.decisionsMutex.Unlock()

		if decision, exists := dm.decisions[decisionID]; exists {
			if decision.Status == protov1.DecisionStatus_DECISION_STATUS_PENDING {
				decision.Status = protov1.DecisionStatus_DECISION_STATUS_TIMEOUT
				dm.finalizeDecision(decision)
			}
		}
	}
}

func (dm *DecisionManager) finalizeDecision(decision *protov1.DistributedDecision) {
	if decision.Status == protov1.DecisionStatus_DECISION_STATUS_TIMEOUT {
		// Handle timeout case
		if len(decision.Votes) > 0 {
			// Use partial results if available
			decision.Status = protov1.DecisionStatus_DECISION_STATUS_DECIDED
		} else {
			decision.Status = protov1.DecisionStatus_DECISION_STATUS_FAILED
			return
		}
	}

	// Calculate final decision based on votes
	var totalVotes float32
	for _, vote := range decision.Votes {
		totalVotes += vote
	}

	// Set final decision
	if totalVotes > 0 {
		decision.Topic = fmt.Sprintf("%.2f", totalVotes/float32(len(decision.Votes)))
		decision.Confidence = float32(len(decision.Votes)) / float32(len(decision.Participants))
		decision.Status = protov1.DecisionStatus_DECISION_STATUS_DECIDED
	} else {
		decision.Status = protov1.DecisionStatus_DECISION_STATUS_FAILED
	}

	// Broadcast final decision
	dm.clusterManager.BroadcastMessage(decision)
}

func calculateNodeWeight(node *protov1.Node) float64 {
	// Base weight
	weight := 1.0

	// Adjust based on node capabilities
	if cpuCap, ok := node.Capabilities["cpu"]; ok {
		weight *= (1.0 + cpuCap)
	}
	if memCap, ok := node.Capabilities["memory"]; ok {
		weight *= (1.0 + memCap)
	}

	// Normalize weight to [0,1]
	return weight / (1.0 + weight)
}

// DecisionActor handles decision-related messages
type DecisionActor struct {
	manager *DecisionManager
	actor.Actor
}

func (da *DecisionActor) Receive(context actor.Context) {
	switch msg := context.Message().(type) {
	case *protov1.DistributedDecision:
		da.handleDecision(msg)
	}
}

func (da *DecisionActor) handleDecision(decision *protov1.DistributedDecision) {
	switch decision.Status {
	case protov1.DecisionStatus_DECISION_STATUS_PENDING:
		// Process new decision request
		da.processDecision(decision)
	case protov1.DecisionStatus_DECISION_STATUS_DECIDED:
		// Handle final decision
		da.handleFinalDecision(decision)
	}
}

func (da *DecisionActor) processDecision(decision *protov1.DistributedDecision) {
	// Submit vote
	vote := float32(0.5) // Default vote value
	if err := da.manager.SubmitVote(decision.Id, da.manager.clusterManager.node.Id, vote); err != nil {
		// Handle error
		fmt.Printf("Failed to submit vote: %v\n", err)
	}
}

func (da *DecisionActor) handleFinalDecision(decision *protov1.DistributedDecision) {
	// Implement final decision handling logic
	fmt.Printf("Final decision for %s: %s\n", decision.Topic, decision.Topic)
}

func evaluateOption(option string) float64 {
	// Implement option evaluation logic
	// This is a placeholder implementation
	return 0.5
}
